package com.ddpt.permission.util;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import java.io.InputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.Date;

/**
 * JWT用私钥证书加密工具类
 *
 * @author zhangsh
 * @data 2021年4月13日
 */
@Slf4j
public class JWTJKSUtil {
    // 本类唯一实例
    private static JWTJKSUtil instance = null;

    // 签名算法
    private static SignatureAlgorithm defaultSignatureAlgorithm = SignatureAlgorithm.RS256;

    // 默认私钥证书文件
    private String keyStoreFile = "./jwt/keystore.jks";
    // 证书密码
    private char[] password;
    // 私钥别名
    private String alias;
    // 证书库
    private KeyStore store;
    // 同步控制锁
    private Object lock = new Object();


    /**
     * 获取本类唯一实例
     *
     * @param alias        证书别名，必传
     * @param password     证书密码，必传
     * @param keyStorePath 证书路径 , 非必传，默认路径:/usr/local/rsa/keystore.jks
     * @return
     * @author zhangsh
     * @data 2021年4月13日
     */
    public static JWTJKSUtil getInstance(String alias, String password, String keyStorePath) {
        synchronized (JWTJKSUtil.class) {
            if (instance == null) {
                synchronized (JWTJKSUtil.class) {
                    instance = new JWTJKSUtil(alias, password.toCharArray(), keyStorePath);
                }
            }
            return instance;
        }
    }

    /**
     * @param alias       证书别名
     * @param password    证书密码
     * @param jksFilePath 证书路径
     */
    private JWTJKSUtil(String alias, char[] password, String jksFilePath) {
        if (null != alias) {
            this.alias = alias;
        } else {
            throw new IllegalArgumentException("证书别名:alias不能为空");
        }

        if (null != password) {
            this.password = password;
        } else {
            throw new IllegalArgumentException("证书别名:password不能为空");
        }

        if (null != jksFilePath) {
            this.keyStoreFile = jksFilePath;
        }
    }

    /**
     * 读取私证书
     *
     * @param alias 证书别名
     * @return
     * @author zhangsh
     * @data 2021年4月13日
     */
    private KeyPair getKeyPair(String alias) {
        return getKeyPair(alias, this.password);
    }

    /**
     * 读取私证书
     *
     * @param alias    证书别名
     * @param password 证书密码
     * @return
     * @author zhangsh
     * @data 2021年4月13日
     */
    private KeyPair getKeyPair(String alias, char[] password) {
        try {
            // 如果加载过证书文件就不再读取文件
            synchronized (this.lock) {
                if (this.store == null) {
                    synchronized (this.lock) {
                        InputStream is = null;
                        if (keyStoreFile.startsWith("http")) {
                            HttpResponse execute = HttpRequest.get(keyStoreFile).execute();
                            is = execute.bodyStream();
                        } else {
                            is = new ClassPathResource(keyStoreFile).getInputStream();
                            // is = new FileInputStream(keyStoreFile);
                        }
                        try {
                            this.store = KeyStore.getInstance("JKS");
                            this.store.load(is, this.password);
                        } finally {
                            if (is != null) {
                                try {
                                    is.close();
                                } catch (Exception e) {
                                    log.warn("关闭 " + keyStoreFile + "输入流时发生错误", e);
                                }
                            }
                        }
                    }
                }
            }

            // 获取私钥
            RSAPrivateCrtKey key = (RSAPrivateCrtKey) this.store.getKey(alias, password);
            // 获取公钥
            RSAPublicKeySpec spec = new RSAPublicKeySpec(key.getModulus(), key.getPublicExponent());
            PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(spec);

            // 缓存起来
            return new KeyPair(publicKey, key);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot load keys from store: " + this.keyStoreFile, e);
        }
    }

    /**
     * 用RSA私钥进行加签，生成JWT token
     *
     * @param issuer     该JWT的签发者，是否使用是可选的
     * @param subject    该JWT所面向的用户，是否使用是可选的
     * @param addience   接收该JWT的一方，是否使用是可选的
     * @param issuedAt   在什么时候签发的(UNIX时间)，是否使用是可选的
     * @param expiration 什么时候过期，这里是一个Unix时间戳，是否使用是可选的；
     * @return
     * @author zhangsh
     * @data 2021年4月13日
     */
    public String generateJWTToken(String issuer, String subject, String addience, Date issuedAt, Date expiration) {
        KeyPair keyPair = this.getKeyPair(alias);

        return Jwts.builder()
                //该JWT的签发者，是否使用是可选的
                .setIssuer(issuer)
                //该JWT所面向的用户，是否使用是可选的
                .setSubject(subject)
                //接收该JWT的一方，是否使用是可选的
                .setAudience(addience)
                //什么时候过期，这里是一个Unix时间戳，是否使用是可选的；
                .setIssuedAt(issuedAt)
                //在什么时候签发的(UNIX时间)，是否使用是可选的
                .setExpiration(expiration)
                //如果当前时间在nbf里的时间之前，则Token不被接受；一般都会留一些余地，比如几分钟；，是否使用是可选的
                // .setNotBefore(DateUtil.date())
                .signWith(defaultSignatureAlgorithm, keyPair.getPrivate())
                .compact();
    }

    public static void main(String[] args) {

        JWTJKSUtil util = JWTJKSUtil.getInstance("testbp.cathaylife.cn", "cathaylife", null);
        String s = util.generateJWTToken("Cathay",
                "d6a9a933c8aafc51e55ac0662b6e4d4a",
                null,
                new Date(),
                null);
        System.out.println(s);
    }

}